package com.laiketui.gateway.controller;

import com.laiketui.core.cache.RedisUtil;
import com.laiketui.core.common.LaiKeGlobleConst;
import com.laiketui.core.common.SplitUtils;
import com.laiketui.core.domain.LaiKeApi;
import com.laiketui.core.domain.Result;
import com.laiketui.core.exception.LaiKeAPIException;
import com.laiketui.core.lktconst.GloabConst;
import com.laiketui.core.lktconst.gwconst.LaiKeGWConst;
import com.laiketui.core.utils.tool.NetworkUtils;
import com.laiketui.root.gateway.GatewayService;
import com.laiketui.root.gateway.dto.LaiKeApiDto;
import com.laiketui.root.gateway.impl.APIServiceFactory;
import com.laiketui.root.gateway.util.I18nUtils;
import com.laiketui.root.gateway.util.LangUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static com.laiketui.core.lktconst.gwconst.LaiKeGWConst.*;

/**
 * @description: 网关入口
 * @author: wx
 */
@RestController("laiKeApiController")
public class LaiKeApiController {

    private final static Logger logger = LoggerFactory.getLogger(LaiKeApiController.class);

    @Autowired
    private RedisUtil redisUtil;

    private GatewayService gatewayService;


    @Autowired
    private APIServiceFactory serviceFactory;


    /**
     * @return
     */
    @RequestMapping("/checkApis")
    @ResponseBody
    public Result checkApis() {
        check();
        return Result.success("SUCCESS");
    }

    @RequestMapping("/gw")
    @ResponseBody
    public Result gate(@NotNull @ModelAttribute LaiKeApiDto dto, HttpServletRequest request) throws IOException {
        Result result = null;
        try {

            String domain = request.getServerName();
            logger.info("请求地址uri:{}", domain);

            if (dto == null) {
                throw new LaiKeAPIException(LaiKeGWConst.GW_BAD_REQ, "网关已启动，本次请求异常");
            }

            String apiCacheKey = getApiKey(dto);
            LinkedList<LaiKeApi> apis = (LinkedList<LaiKeApi>) redisUtil.get(apiCacheKey);
            if (apis == null) {
                logger.error("服务未启动或接口不存在:" + apiCacheKey);
                throw new LaiKeAPIException(LaiKeGWConst.API_OFFLINE, "服务【" + apiCacheKey + "】未启动或不存在");
            }

            //获取api调用对象
            LaiKeApi api = getApiObj(apiCacheKey, apis);

            //鉴权
            /*if (!doAuth(dto, request, api)) {
                throw new LaiKeAPIException(LaiKeGWConst.RESCODE_NOAUTH,"无操作权限");
            }*/

            gatewayService = serviceFactory.getGatewayService(api.getProtocol());
            result = gatewayService.invoke(api, dto);

        } catch (LaiKeAPIException e) {
            e.printStackTrace();
            logger.error(e.getMessage());
            result = Result.error(e.getCode(), e.getMessage(), e.getMethod());
        }

        return getResult(result, dto);
    }

    /**
     * 随机负载/hash/ （无需数据库或者redis配合）或者最小活跃度/轮询（需要数据库或者redis配合） 默认随机
     *
     * @param apiCacheKey
     * @param apis
     * @return
     */
    private synchronized LaiKeApi getApiObj(String apiCacheKey, LinkedList<LaiKeApi> apis) throws LaiKeAPIException {
        int randomApiPos = new Random().nextInt(apis.size());
        LaiKeApi api = apis.get(randomApiPos);
        //接口所在节点不在线 分布式的时候需要有负载策略
        int reconnect = 3;
        do {
            logger.debug("接口检测：" + api);
            boolean canConnect = NetworkUtils.isHostConnectable(api.getNodeIp(), api.getPort(), api.getTimeout());

            if (canConnect) {
                break;
            }

            // 三次 默认24秒
            if (reconnect == 1) {
                reconnect = 3;
                throw new LaiKeAPIException(LaiKeGWConst.API_OFFLINE, "暂无可用服务，请稍后再试");
            }

            //无可使用接口
            if (apis.size() == 0) {
                //删除对应Redis里面的无用API服务注册信息redisUtil.del(apiCacheKey);
                logger.error("负载时发现接口api：{} 所在节点{}:{}离线", apiCacheKey, api.getNodeIp(), api.getPort());
                throw new LaiKeAPIException(LaiKeGWConst.API_OFFLINE, "无可用服务");
            }
            //继续从剩下的接口集合中随机获取接口
            randomApiPos = new Random().nextInt(apis.size());
            api = apis.get(randomApiPos);
            reconnect--;
        } while (reconnect >= 1);

        return api;
    }

    private final int MAX_CONCURRENT_REQUESTS = 10; // 最大并发请求数

    /**
     * 检查接口是否可用 ，并把不可用的接口从注册中心下线【即删除】
     */
    public void check() {
        ExecutorService executor = Executors.newFixedThreadPool(MAX_CONCURRENT_REQUESTS);
        try {
            Set<String> keys = redisUtil.keyScan(LaiKeGlobleConst.RedisCacheKeyPre.LAIKE_API + "*");
            Iterator<String> it = keys.iterator();
            while (it.hasNext()) {
                String apiCacheKey = it.next();
                executor.execute(new LaiKeApiChecker(redisUtil, apiCacheKey));
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("检查接口出错!");
        } finally {
            // 等待所有任务执行完毕后关闭线程池
            executor.shutdown();
        }

    }


    /**
     * 获取api的key
     *
     * @param dto
     * @return
     */
    public String getApiKey(LaiKeApiDto dto) throws LaiKeAPIException {

        //获取请求的接口
        String apiKey = dto.getApi();

        if (StringUtils.isEmpty(apiKey)) {
            String module = dto.getModule();
            String app = dto.getApp();
            String action = dto.getAction();
            if (StringUtils.isEmpty(module) || StringUtils.isEmpty(action)) {
                throw new LaiKeAPIException(LaiKeGWConst.GW_BAD_REQ, "异常请求");
            }
            if (StringUtils.isEmpty(app)) {
                String method = dto.getM();
                if (StringUtils.isEmpty(method)) {
                    app = API_DEFAULT_METHOD;
                } else {
                    app = method;
                }
            }
            apiKey = module.trim() + SplitUtils.XSD + action.trim() + SplitUtils.XSD + app.trim();
            logger.info("请求api:" + apiKey);
        }

        String version = dto.getVersion();
        if (StringUtils.isEmpty(version)) {
            version = API_VERSION;
        }

        String apiCacheKey = LaiKeGlobleConst.RedisCacheKeyPre.LAIKE_API + SplitUtils.UNDERLIEN + apiKey + SplitUtils.UNDERLIEN + version;
        return apiCacheKey;
    }

    /**
     * 需要登陆才能使用的接口 判断是否登陆了
     * 返回true表示通过认证 false 表示没有通过认证
     *
     * @param dto
     * @param request
     * @param api
     * @return
     */
    private boolean doAuth(@ModelAttribute @NotNull LaiKeApiDto dto, HttpServletRequest request, LaiKeApi api) {
        //权限认证 true 表示需要登陆才能掉用 false表示不需要登陆就能调用
        boolean islogin = api.getLogin();
        //需要认证
        if (islogin) {
            String token = request.getHeader(HEARDER_TOKEN);
            int storeType = dto.getStore_type();
            if (token == null || "".equals(token)) {
                token = dto.getAccess_id();
            }
            // 管理后台
            Object cacheValue = redisUtil.get(com.laiketui.core.lktconst.GloabConst.RedisHeaderKey.LOGIN_ACCESS_MANAGE_TOKEN + token);
            if (cacheValue == null) {
                //店铺后台
                cacheValue = redisUtil.get(com.laiketui.core.lktconst.GloabConst.RedisHeaderKey.LOGIN_ACCESS_MCH_TOKEN + token);
            }

            if (cacheValue == null) {
                //移动端用户
                cacheValue = redisUtil.get(com.laiketui.core.lktconst.GloabConst.RedisHeaderKey.LOGIN_ACCESS_TOKEN + token);
            }

            if (cacheValue == null) {
                //PC商城用户
                cacheValue = redisUtil.get(com.laiketui.core.lktconst.GloabConst.RedisHeaderKey.LOGIN_ACCESS_PC_SHOP_TOKEN + token);
            }

            if (cacheValue == null) {
                //PC商城用户
                cacheValue = redisUtil.get(com.laiketui.core.lktconst.GloabConst.RedisHeaderKey.LOGIN_ACCESS_SUPPLIER_TOKEN + token);
            }

            if (cacheValue == null) {
                //门店核销PC端
                cacheValue = redisUtil.get(com.laiketui.core.lktconst.GloabConst.RedisHeaderKey.LOGIN_AUTO_LOGIN_MCH_SON_PC_TOKEN + token);
            }

            if (cacheValue == null) {
                //门店核销移动端
                cacheValue = redisUtil.get(com.laiketui.core.lktconst.GloabConst.RedisHeaderKey.LOGIN_AUTO_LOGIN_MCH_SON_APP_TOKEN + token);
            }

            if (cacheValue == null) {
                return false;
            }
        }
        return true;
    }

    public Object getCacheUser(String key) {
        return redisUtil.get(key);
    }


    /**
     * 获取国际化提示信息
     *
     * @param result
     */
    private Result getResult(Result result, LaiKeApiDto dto) throws LaiKeAPIException {
        try {
            //设置国际化语言类型
            if (dto.getLanguage() == null) {
                LangUtils.setLanguage(GloabConst.Lang.JAVA_CN);
            } else if (GloabConst.Lang.JAVA_CN.equalsIgnoreCase(dto.getLanguage())) {
                LangUtils.setLanguage(GloabConst.Lang.JAVA_CN);
            } else if (GloabConst.Lang.JAVA_EN.equalsIgnoreCase(dto.getLanguage())) {
                LangUtils.setLanguage(GloabConst.Lang.JAVA_EN);
            } else {
                LangUtils.setLanguage(GloabConst.Lang.JAVA_CN);
            }

            String exportType = dto.getExportType();
            if (result == null && !LaiKeGWConst.EXP_FLAG.equals(exportType)) {
                return Result.error("返回结果为空");
            } else if (LaiKeGWConst.EXP_FLAG.equals(exportType)) {
                return result;
            }

//            Object cache = redisUtil.get(GloabConst.RedisHeaderKey.LOGIN_ACCESS_TOKEN + dto.getAccess_id());
//            User user = null;
//            if (cache != null) {
//                user = JSON.parseObject(cache.toString(), User.class);
//            }
//            if (user != null) {
//                if (StringUtils.isNotEmpty(user.getLang())) {
//                    if (GloabConst.Lang.PHP_EN_GB.equalsIgnoreCase(user.getLang())) {
//                        LangUtils.setLanguage(GloabConst.Lang.JAVA_EN);
//                    } else {
//                        LangUtils.setLanguage(GloabConst.Lang.JAVA_CN);
//                    }
//                }
//            }
//
//            if (dto.getStore_type() != null && dto.getStore_type() == 8) {
//                if (StringUtils.isNotEmpty(result.getCode())) {
//                    String tips = I18nUtils.getMessage(result.getCode());
//                    if (StringUtils.isNotEmpty(tips)) {
//                        result.setMessage(tips);
//                    }
//                }
//            }

            //设置国际化语言返回提示
//            if (StringUtils.isNotEmpty(result.getCode())) {
//                String tips = I18nUtils.getMessage(result.getCode());
//                if (StringUtils.isNotEmpty(tips)) {
//                    result.setMessage(tips);
//                } else {
//                    logger.error("国际化code没有对应资源,code:{}", result.getCode());
//                }
//                return result;
//            }
            return result;
        } catch (LaiKeAPIException e) {
            e.printStackTrace();
            result.setMessage(e.getMessage());
//            logger.error(JSON.toJSONString(e));
            return result;
        } catch (Exception e) {
            e.printStackTrace();
//            logger.error(JSON.toJSONString(e));
            result.setMessage(e.getMessage());
            return result;
        }

    }
}